package ru.michaeltyan.familyconnector.web;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

import ru.michaeltyan.familyconnector.beans.Foodstuff;
import ru.michaeltyan.familyconnector.beans.FoodstuffList;
import ru.michaeltyan.familyconnector.businessobjects.FoodstuffListBusinessObject;
import ru.michaeltyan.familyconnector.businessobjects.FoodstuffManagerBusinessObject;
import ru.michaeltyan.familyconnector.cache.Cache;

public class FoodstuffsController implements Controller {

	protected final Log logger = LogFactory.getLog(getClass());

	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {
		HttpSession session = request.getSession(true);
		ApplicationContext appContext = (ApplicationContext) session.getAttribute("appContext");
		FoodstuffManagerBusinessObject foodstuffManagerBo = (FoodstuffManagerBusinessObject) appContext.getBean("foodstuffManagerLFUBo");
		FoodstuffListBusinessObject foodstuffListBo = (FoodstuffListBusinessObject) appContext.getBean("foodstuffListBo");
		if (request.getParameter("action").toString().equals("showList")) {
			return createShowListModelAndView(request, foodstuffManagerBo, foodstuffListBo);
		} else {
			return createCacheModelAndView(request, appContext, foodstuffManagerBo, foodstuffListBo);
		}
	}

	private ModelAndView createCacheModelAndView(HttpServletRequest request, ApplicationContext appContext, FoodstuffManagerBusinessObject foodstuffManagerBo, FoodstuffListBusinessObject foodstuffListBo) {
		int cache1LevelSize = Integer.parseInt(request.getParameter("cache1LevelSize"));
		int cache2LevelSize = Integer.parseInt(request.getParameter("cache2LevelSize"));
		String cacheType = request.getParameter("cacheType");
		int numberOfObjects = Integer.parseInt(request.getParameter("numberOfObjects"));
		List<Long> randomIds = getNumberOfFoodstuffListIdsRandomized(foodstuffManagerBo, numberOfObjects);
		long hiberTime = getTimeOfFoodstuffsSearchingViaHibernate(foodstuffManagerBo, foodstuffListBo, randomIds);
		ModelAndView modelAndView = new ModelAndView("cache");
		try {
			long cacheTime = getTimeOfFoodstuffsSearchingViaCache(appContext, foodstuffManagerBo, cache1LevelSize, cache2LevelSize, cacheType, randomIds);
			modelAndView = addMethodImplementationTimeToModelAndView(modelAndView, cacheType, hiberTime, cacheTime);
		} catch (FileNotFoundException e) {
			modelAndView = addErrorDescriptionToModelAndView(modelAndView, e, "FileNotFoundException occured while object restored from cache.");

		} catch (IOException e) {
			modelAndView = addErrorDescriptionToModelAndView(modelAndView, e, "The error occured while cache object was stored to/restored from file system.");

		} catch (ClassNotFoundException e) {
			modelAndView = addErrorDescriptionToModelAndView(modelAndView, e, "The error occured while object was restored. File is broken.");
		} catch (SQLException e) {
			modelAndView = addErrorDescriptionToModelAndView(modelAndView, e, "Connection with database error.");
		}
		return modelAndView;
	}

	private ModelAndView addErrorDescriptionToModelAndView(ModelAndView modelAndView, Exception e, String messageToUser) {
		modelAndView.addObject("errorMessage", messageToUser);
		modelAndView.addObject("stackTrace", e.getStackTrace());
		modelAndView.addObject("eMessage", e.getMessage());
		return modelAndView;
	}

	private ModelAndView createShowListModelAndView(HttpServletRequest request, FoodstuffManagerBusinessObject foodstuffManagerBo, FoodstuffListBusinessObject foodstuffListBo) {
		long foodstuffListId = Long.parseLong(request.getParameter("foodstuffLists"));
		FoodstuffList foodstuffList = foodstuffListBo.findById(foodstuffListId);
		List<Foodstuff> foodstuffs = foodstuffManagerBo.findByList(foodstuffList);
		ModelAndView modelAndView = new ModelAndView("foodstuffs");
		if (foodstuffs != null && foodstuffs.size() > 0) {
			modelAndView.addObject("foodstuffs", foodstuffs);
		}
		return modelAndView;
	}

	private ModelAndView addMethodImplementationTimeToModelAndView(ModelAndView modelAndView, String cacheType, long hiberTime, long cacheTime) {
		String timeString;
		if (cacheType.equals("hibernate")) {
			timeString = "Time of standard execution via hibernate: " + hiberTime;
		} else {
			timeString = "Time of execution via cache with " + cacheType + " strategy: " + cacheTime;
		}
		modelAndView.addObject("timeString", timeString);
		return modelAndView;
	}

	private long getTimeOfFoodstuffsSearchingViaCache(ApplicationContext appContext, FoodstuffManagerBusinessObject foodstuffManagerBo, int cache1LevelSize, int cache2LevelSize, String cacheType, List<Long> randomIds)
			throws IOException, ClassNotFoundException, SQLException {
		long startTime = 0;
		long finishTime = 0;
		if (!cacheType.equals("hibernate")) {
			Cache cache = (Cache) appContext.getBean("cache" + cacheType);
			cache.setSizeOfFirstLevelCache(cache1LevelSize);
			cache.setSizeOfSecondLevelCache(cache2LevelSize);
			startTime = System.currentTimeMillis();
			proccessFoodstuffExtractingViaCache(foodstuffManagerBo, randomIds, cache);
			finishTime = System.currentTimeMillis();
		}
		long cacheTime = finishTime - startTime;
		return cacheTime;
	}

	private long getTimeOfFoodstuffsSearchingViaHibernate(FoodstuffManagerBusinessObject foodstuffManagerBo, FoodstuffListBusinessObject foodstuffListBo, List<Long> randomIds) {
		long startHiberTime = System.currentTimeMillis();
		proccessFoodstuffExtractingViaHibernate(foodstuffManagerBo, foodstuffListBo, randomIds);
		long finishHiberTime = System.currentTimeMillis();
		long hiberTime = finishHiberTime - startHiberTime;
		return hiberTime;
	}

	private void proccessFoodstuffExtractingViaCache(FoodstuffManagerBusinessObject foodstuffManagerBo, List<Long> randomIds, Cache cache) throws IOException, ClassNotFoundException, SQLException {
		FoodstuffList foodstuffList = new FoodstuffList();
		foodstuffManagerBo.setCache(cache);
		for (long id : randomIds) {
			foodstuffList.setId(id);
			foodstuffManagerBo.findByListWithCache(foodstuffList);
		}
	}

	private void proccessFoodstuffExtractingViaHibernate(FoodstuffManagerBusinessObject foodstuffManagerBo, FoodstuffListBusinessObject foodstuffListBo, List<Long> randomIds) {
		for (long id : randomIds) {
			FoodstuffList foodstuffList = foodstuffListBo.findById(id);
			foodstuffManagerBo.findByList(foodstuffList);
		}
	}

	private List<Long> getNumberOfFoodstuffListIdsRandomized(FoodstuffManagerBusinessObject foodstuffManagerBo, int numberOfObjects) {
		List<FoodstuffList> foodstuffLists = foodstuffManagerBo.findLists();
		List<FoodstuffList> shuffledFoodstuffLists = shuffleList(foodstuffLists, numberOfObjects);
		List<Long> randomIds = new ArrayList<Long>();
		for (FoodstuffList foodstuffList : shuffledFoodstuffLists) {
			long id = foodstuffList.getId();
			randomIds.add(id);
		}
		return randomIds;
	}

	private List<FoodstuffList> shuffleList(List<FoodstuffList> foodstuffLists, int numberOfObjects) {
		List<FoodstuffList> shuffledFoodstuffList = new ArrayList<FoodstuffList>();
		int size = foodstuffLists.size();
		while (shuffledFoodstuffList.size() != numberOfObjects) {
			int r = (int) (Math.random() * (size));
			shuffledFoodstuffList.add(foodstuffLists.get(r));
		}
		return shuffledFoodstuffList;
	}

}
